package we.dispatcher;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.concurrent.BlockingQueue;
import org.json.JSONException;
import org.json.JSONObject;
import we.common.CCommonHandler;
import we.config.CConfigHandler;
import we.config.CConstant;
import we.core.bean.CDataContainer;
import we.core.handlers.CAbstractCoreHandler;
import we.extension.IExtension;

public class CReadHandler implements Runnable {

    BlockingQueue<SelectionKey> in;
    Charset charset;
    CharsetDecoder decoder;
    ByteBuffer buffer;

    public CReadHandler(BlockingQueue<SelectionKey> in) {
        this.in = in;
        charset = Charset.forName(CConstant.DEFAULT_ENCODING);
        decoder = charset.newDecoder();
        buffer = ByteBuffer.allocate(CConstant.MAX_MESSAGE_LENGTH);
    }

    @Override
    public void run() {
        while (true) {
            SelectionKey key = null;
            try {
                key = in.take();
                String message = handleKey(key);
                System.out.println("Message : " + message);
                if (message != null) {
                    System.out.println("Message : " + message);
                    JSONObject json = new JSONObject(message);
                    if (json.has("ext")) {
                        IExtension extension =
                                CDataContainer.instance().getExtension(json.getString("ext"));
                        if (extension != null) {
                            extension.handleClientRequest(json, CDataContainer.instance().getUserByChannel((SocketChannel) key.channel()));
                        }
                    } else if (json.has("handler")) {
                        CAbstractCoreHandler handler =
                                CDataContainer.instance().getCoreHandler(json.getString("handler"));
                        if (handler != null) {
                            handler.handleEvent((SocketChannel) key.channel(), json);
                        }
                    }
                }
            } catch (InterruptedException e) {
                CCommonHandler.writeErrLog(e);
            } catch (JSONException jse) {
                CCommonHandler.writeErrLog(jse);
                // TODO
                SocketChannel channel = (SocketChannel) key.channel();
                try {
                    System.out.println("parser json false. Close connection");
                    channel.close();
                } catch (IOException e) {
                    CCommonHandler.writeErrLog(e);
                }
                handleDisconnect(channel);
            }
        }
    }

    private String handleKey(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
        int byteread;
        buffer.limit(2);
        buffer.rewind();
        try {
            byteread = channel.read(buffer);
            if (byteread == 2) {
                buffer.flip();
                int length =
                        ((buffer.get() & 0xFF) << 8) + (buffer.get() & 0xFF);
                if (length > 0) {
                    buffer.limit(length);
                    buffer.rewind();
                    byteread = channel.read(buffer);
                    if (byteread < length) {
                        if (length == 15472) { // Policy file request
                            CDispatcherHandler.instance().sendPolicy(channel);
                            key.attach(false);
                            return null;
                        } else {
                            Selector readSelector = Selector.open();
                            channel.register(readSelector,
                                    SelectionKey.OP_READ);
                            int code =
                                    readSelector.select(CConfigHandler.instance().getLong("read.timeout"));
                            if (code == 1) {
                                byteread += channel.read(buffer);
                            }
                            if (byteread
                                    < length) { // Slow clients, kill them all
                                System.out.println("Slow connection. Close!!!");
                                key.cancel();
                                channel.close();
                                handleDisconnect(channel);
                                return null;
                            }
                        }
                    }
                    buffer.flip();
                    key.attach(false);
                    return decoder.decode(buffer).toString();
                } else {
                    System.out.println("Length <0. Close!!!!!!!!!");
                    key.cancel();
                    channel.close();
                    handleDisconnect(channel);
                }
            } else if (byteread < 0) {
                System.out.println("byteRead =" + byteread
                        + " < 2. Close!!!!!!!!!");
                key.cancel();
                channel.close();
                handleDisconnect(channel);
            }
        } catch (CharacterCodingException e) {
            CCommonHandler.writeErrLog(e);
        } catch (IOException e) {
            CCommonHandler.writeErrLog(e);
            try {
                System.out.println("Read false. Close connection");
                channel.close();
                handleDisconnect(channel);
            } catch (IOException f) {
                CCommonHandler.writeErrLog(f);
            }
        }
        return null;
    }

    private void handleDisconnect(SocketChannel channel) {
        CAbstractCoreHandler handler =
                CDataContainer.instance().getCoreHandler("disconnect");
        if (handler != null) {
            handler.handleEvent(channel, null);
        }
    }
}
